﻿#include "IModule.h"

#include "ModulesManager.h"

#include "../../Utils/Logger.h"

IModule::IModule(Category c,
				 std::array<std::u8string_view, languagesize> n,
				 std::array<std::u8string_view, languagesize> t,
				 int key,
				 fast_io::vector<SettingEntry> s,
				 void (*_init)(IModule*),
				 void (*_onEnable)(),
				 void (*_onDisable)(),
				 void (*_onWorldTick)(C_GameMode*),
				 void (*_onTick)(C_GameMode*),
				 void (*_onKeyUpdate)(uint8_t, bool),
				 void (*_onAttack)(C_Entity*),
				 void (*_onPreRender)(C_MinecraftUIRenderContext*),
				 void (*_onPostRender)(C_MinecraftUIRenderContext*),
				 void (*_onLevelRender)(),
				 void (*_onMove)(C_MoveInputHandler*),
				 void (*_onSendPacket)(C_Packet*, bool&),
				 void (*_onPlayerTick)(C_Player*),
				 void (*_onGetPickRange)(),
				 void (*_onUpdate)()) : category(c),
										name(std::move(n)),
										tooltip(std::move(t)),
										keybind(key),
										settings(std::move(s)),
										init(_init),
										onEnable(_onEnable),
										onDisable(_onDisable),
										onWorldTick(_onWorldTick),
										onTick(_onTick),
										onKeyUpdate(_onKeyUpdate),
										onAttack(_onAttack),
										onPreRender(_onPreRender),
										onPostRender(_onPostRender),
										onLevelRender(_onLevelRender),
										onMove(_onMove),
										onSendPacket(_onSendPacket),
										onPlayerTick(_onPlayerTick),
										onGetPickRange(_onGetPickRange),
										onUpdate(_onUpdate) {
	std::u8string tempname(name.front());
	std::ranges::for_each(tempname, [](char8_t& str) { str = fast_io::char_category::to_c_lower(str); });
	ModuleManager::NameTable[tempname] = this;
}

void IModule::_init() {
	if (init) {
		auto look = std::shared_lock(iModuleLook);
		init(this);
	}
}

void IModule::_onEnable() {
	if (onEnable) {
		auto look = std::shared_lock(iModuleLook);
		onEnable();
	}
}

void IModule::_onDisable() {
	if (onDisable) {
		auto look = std::shared_lock(iModuleLook);
		onDisable();
	}
}

uint8_t IModule::getKeybind() const {
	return keybind;
}

void IModule::setKeybind(uint8_t key) {
	keybind = key;
}

void IModule::_onWorldTick(C_GameMode* gm) {
	if (onWorldTick) {
		auto look = std::shared_lock(iModuleLook);
		onWorldTick(gm);
	}
}

void IModule::_onTick(C_GameMode* gm) {
	if (onTick) {
		auto look = std::shared_lock(iModuleLook);
		onTick(gm);
	}
}

void IModule::_onKeyUpdate(uint8_t key, bool isDown) {
	if (onKeyUpdate == nullptr) [[likely]] {
		if (isDown && key == keybind)
			toggle();
	} else {
		auto look = std::shared_lock(iModuleLook);
		onKeyUpdate(key, isDown);
	}
}

void IModule::_onAttack(C_Entity* ent) {
	if (onAttack) {
		auto look = std::shared_lock(iModuleLook);
		onAttack(ent);
	}
}

void IModule::_onPreRender(C_MinecraftUIRenderContext* renderCtx) {
	if (onPreRender) {
		auto look = std::shared_lock(iModuleLook);
		onPreRender(renderCtx);
	}
}

void IModule::_onPostRender(C_MinecraftUIRenderContext* renderCtx) {
	if (onPostRender) {
		auto look = std::shared_lock(iModuleLook);
		onPostRender(renderCtx);
	}
}

void IModule::_onLevelRender() {
	if (onLevelRender) {
		auto look = std::shared_lock(iModuleLook);
		onLevelRender();
	}
}

void IModule::_onMove(C_MoveInputHandler* m) {
	if (onMove) {
		auto look = std::shared_lock(iModuleLook);
		onMove(m);
	}
}

void IModule::_onSendPacket(C_Packet* p, bool& b) {
	if (onSendPacket) {
		auto look = std::shared_lock(iModuleLook);
		onSendPacket(p, b);
	}
}

void IModule::_onPlayerTick(C_Player* p) {
	if (onPlayerTick) {
		auto look = std::shared_lock(iModuleLook);
		onPlayerTick(p);
	}
}

void IModule::_onGetPickRange() {
	if (onGetPickRange) {
		auto look = std::shared_lock(iModuleLook);
		onGetPickRange();
	}
}

void IModule::_onUpdate() {
	if (onUpdate) {
		auto look = std::shared_lock(iModuleLook);
		onUpdate();
	}
}

void IModule::onLoadConfig(nlohmann::json& conf) {
	auto look = std::shared_lock(iModuleLook);
	auto& tempfront = name.front();
	auto& tempstringview = *reinterpret_cast<std::string_view*>(&tempfront);
	if (conf.contains(tempstringview)) {
		auto& obj = conf.at(tempstringview);
		if (obj.is_null())
			return;
		if (obj.contains("keybind")) 
			keybind = obj.at("keybind").get<uint8_t>();
		if (obj.contains("enabled"))
			enabled = obj.at("enabled").get<bool>();
		for (auto& it : settings) {
			if (auto name = *reinterpret_cast<std::string_view*>(&it.name); obj.contains(name)) {
				auto& value = obj.at(name);
				if (value.is_null())
					continue;
				try {
					switch (it.valueType) {
					case ValueType::FLOAT_T:
						*it.value._float = value.get<float>();
						break;
					case ValueType::INT_T:
						*it.value._int = value.get<int>();
						break;
					case ValueType::BOOL_T:
						*it.value._bool = value.get<bool>();
						break;
					case ValueType::ENUM_T:
						it.value.Enum->selected = value.get<int>();
						break;
					}
					it.makeSureTheValueIsAGoodBoiAndTheUserHasntScrewedWithIt();
					continue;
				} catch (std::exception& e) {
					Logger::LogC(LogGrade::warning, u8"Config: ", tempfront, u8" Load Fail: ", fast_io::mnp::os_c_str(reinterpret_cast<const char8_t*>(e.what())));
				}
			}
		}
		if (enabled)
			_onEnable();
	}
}

void IModule::onSaveConfig(nlohmann::json& conf) {
	auto look = std::shared_lock(iModuleLook);
	auto& tempfront = name.front();
	auto& tempstringview = *reinterpret_cast<std::string_view*>(&tempfront);
	if (conf.contains(tempstringview))
		conf.erase(tempstringview);

	nlohmann::json obj{};
	
	obj.emplace("keybind", keybind);
	obj.emplace("enabled", last_enbale);

	for (auto& sett : settings) {
		switch (sett.valueType) {
		case ValueType::FLOAT_T:
			obj.emplace(*reinterpret_cast<std::string_view*>(&sett.name), *sett.value._float);
			break;
		case ValueType::INT_T:
			obj.emplace(*reinterpret_cast<std::string_view*>(&sett.name), *sett.value._int);
			break;
		case ValueType::BOOL_T:
			obj.emplace(*reinterpret_cast<std::string_view*>(&sett.name), *sett.value._bool);
			break;
		case ValueType::ENUM_T:
			obj.emplace(*reinterpret_cast<std::string_view*>(&sett.name), sett.value.Enum->selected);
			break;
		}
	}

	conf.emplace(tempstringview, std::move(obj));
}

void IModule::setEnabled(bool e) {
	if (enabled != e) {
		enabled = e;
		enabled ? _onEnable() : _onDisable();
		if (enabled)
			Logger::LogC(LogGrade::info, u8"Enabled " , name.front());
		else
			Logger::LogC(LogGrade::info, u8"Disabled ", name.front());
	}
}

void IModule::toggle() {
	setEnabled(!enabled);
}

bool IModule::isEnabled() const {
	return enabled;
}
